/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "batch.h"

#include <cstring>
#include <fstream>

const std::string default_class = "Main";

batch* batch::instance_ = corelib::null;

batch::batch(){
    flags_ = 0;
    // configure parser & vm
    vm.setHnd( hnd );
    parser.setErrorHnd( hnd );
    parser.setVMachine( vm );
}

batch::~batch(){

}

batch &batch::get( void ){
    if( instance_==corelib::null ){
        instance_ = new batch();
    }
    return *instance_;
}

corelib::int32 batch::parse( const std::string &main_argument, const std::string &file )
{
    // flags
    bool is_open;
    // parse file
    parser.parseFFile( is_open, file.c_str() );
    // if it's open check errors
    if ( is_open ){
        if ( !parser.error() ){
			if( flags_ & DISSAMBLING ){
				std::ofstream ofs( main_argument.c_str() );
				// check if it's open
			    if ( !ofs.is_open() ){
			        // the file couldn't be open
			        std::cout << "the file '" << main_argument << "' couldn't be open" << std::endl;
					return -1;
			    }else{
					vm.dissamble( ofs );
					return 0;
				}
			}else if( flags_ & DEBUGGING ){
				debug( main_argument );
			}else{
				// load arguments
				vm.push_null(); // this
				vm.push_integer( 1 ); // first argument
				// run virtual machine
				vm.run( corelib::ustring( main_argument ), corelib::ustring("meth_main_1_") );
				if( !hnd.derr ){
					// get returning register		
					const paper::register_t &ret = vm.lret();
					// check returned value
					if( ret.type_!=paper::T_INTEGER ){
						std::cout << "error: the returned value is not an integer" << std::endl;
						return -1;
					}else{
						return ret.val.i_;
					}
				}
			}
		}
	}else{
		// the file couldn't be open
		std::cout << "the file '" << file << "' couldn't be open" << std::endl;
		return -1;
	}
}

void batch::debug( const std::string &class_name ){
    char command[1024];
    // print header
    std::cout << "rockscript debugger; type 'help' to get the command list" << std::endl;
    // get debugging commands
    for(;;){
		// print command prompt
		std::cout << ">>> "; 
		// get a command line
		std::cin.getline( command, 1024 );
		// check out commands
		if( !strcmp( command, "q" ) || !strcmp( command, "quit" ) ){
			break;
		}else if( !strcmp( command, "h" ) || !strcmp( command, "help" ) ){
			printDebugHelp();
		}else{
			std::cout << "unknown command!" << std::endl;
		}
    }
}

void batch::printDebugHelp( void )
{
    std::cout << "rockscript command list: " << std::endl;
    std::cout << "  'h/help' print the command list" << std::endl;
    std::cout << "  'q/quit' close debugger" << std::endl;
    std::cout << "  -- variable commands --" << std::endl;
    std::cout << "  'aw/addwatch \"Variable\"' add local variable watch" << std::endl;
    std::cout << "  'cw/clearwatch' clear watch list" << std::endl;
    std::cout << "  'dw/delwatch \"Variable\"' delete local variable watch" << std::endl;
    std::cout << "  -- break-point commands --" << std::endl;
    std::cout << "  'abp/addbp \"Class::Function\" [LineNumber]' add breakpoint" << std::endl;
    std::cout << "  'bpl/bplist' print breakpoint list" << std::endl;
    std::cout << "  'cbp/clearbp' clear breakpoint list" << std::endl;
    std::cout << "  'dbp/delbp <BreakPointIdx>' delete breakpoint" << std::endl;
    std::cout << "  -- execution commands --" << std::endl;
    std::cout << "  'n/next' execute the entire procedure" << std::endl;
    std::cout << "  'r/run' execute script until it reaches a breakpoint" << std::endl;
    std::cout << "  's/step' go inside a procedure" << std::endl;
    std::cout << "  -- state commands --" << std::endl;
    std::cout << "  'vmi/vminf' print virtual machine information" << std::endl;
    std::cout << "  'ml/mlist \"Class\"' print the method list of a class" << std::endl;
    std::cout << "  'rs/rstate' print routine state (counters, flags...)" << std::endl;
    std::cout << "  'sr/setroutine <Idx>' set active routine" << std::endl;
    std::cout << "  'st/strace' print stack trace" << std::endl;
}

void batch::printHelp( void )
{
    std::cout << std::endl;
    std::cout << "Usage is: rock [options] source-file [script-arguments]" << std::endl;
    std::cout << "  -C ClassName ClassName::main() will be called instead of Main::main()" << std::endl;
    std::cout << "  -D FilePath compile and print dissambled code on FilePath" << std::endl;
    std::cout << "  -g compile and run in debugging mode" << std::endl;
    std::cout << std::endl;
}

int batch::main( int argc, char* argv[] )
{
    std::string file_path = "";
    std::string main_argument = "Main";

    // get argumments
    if ( argc < 2 ) { 
        std::cout << "Usage is: rock [options] source-file [script-arguments]" << std::endl;
        std::cout << "Use '-h' to get some help" << std::endl;
        return 0;
    }else{ 
        for (int i = 1; i < argc; i++) { 
			if (strcmp(argv[i],"-h")==0){
				printHelp();
				return 0;
			}else if(strcmp(argv[i],"-D")==0){
				if( flags_ & ( DISSAMBLING | DEBUGGING ) ){
					std::cout << "you can't specify run in dissambling or debugging mode more than once" << std::endl;
					return 1;
				}else{
					if( ++i != argc ){
						FLAGS_SET(flags_,DISSAMBLING);
						main_argument = std::string(argv[i]);
					}else{
						std::cout << "you must specify a FilePath after '-D'" << std::endl;
						return 1;
					}
				}
			}else if(strcmp(argv[i],"-g")==0){
				if( flags_ & ( DISSAMBLING | DEBUGGING ) ){
					std::cout << "you can't specify run in dissambling or debugging mode more than once" << std::endl;
					return 1;
				}else{
					FLAGS_SET(flags_,DEBUGGING);
				}
			}else if(strcmp(argv[i],"-C")==0){
				if( ++i != argc ){
					if(main_argument.compare(default_class)!=0){
						std::cout << "you can't define the Class more than once" << std::endl;
						return 1;
					}
					main_argument = std::string(argv[i]);
				}else{
					std::cout << "you must specify a class after '-C'" << std::endl;
					return 1;
				}
			}else if(strncmp(argv[i],"-",1)==0){
				std::cout << "argument '" << argv[i] << "' invalid" << std::endl;
				return 1;
			}else{
				// get file-path
				file_path = std::string(argv[i]);
				// get script arguments
				for (; i < argc; i++) {
					args_.push_back(argv[i]);
				}
				break;
			}
        }
		// check file-path definition
		if(file_path.compare("")==0){
			std::cout << "you must specify a source-file" << std::endl;
			return 1;
		}
		// parse file
        return parse( main_argument, file_path );
    }
}

int main( int argc, char* argv[] ){
    return batch::get().main( argc, argv );
}

